/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_httpd.htm
 *
 * Zhiqim Httpd is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.httpd.bio;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;

import org.zhiqim.httpd.HttpListener;
import org.zhiqim.httpd.HttpServer;
import org.zhiqim.httpd.model.HttpCertificate;
import org.zhiqim.httpd.model.HttpSslConfig;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Closes;
import org.zhiqim.kernel.util.Hexs;
import org.zhiqim.kernel.util.Sockets;
import org.zhiqim.kernel.util.Validates;

/**
 * HTTPS阻塞式监听器
 *
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 */
public class HttpBioSslListener extends HttpListener
{
    private static final String SSL_SESSION_CACHE = HttpCertificate.class.getName();
    private static final String SSL_NULL_WITH_NULL_NULL = "SSL_NULL_WITH_NULL_NULL";
    
    private final HttpSslConfig ssl;
    private SSLServerSocket serverSocket;
    
    /** 构造函数 */
    public HttpBioSslListener(HttpServer server)
    {
        super(server);
        this.ssl = new HttpSslConfig();
    }
    
    @Override /** 协议 */
    public String getScheme()
    {
        return _HTTPS_;
    }

    @Override /** 线程启动前 */
    protected boolean openBefore()
    {
        try
        {
            //优先SSL参数（五项，其中Keystore两项必须）
            this.ssl.initParam(listenerGroup);
            
            //监听参数和线程池
            super.openBefore();
            
            //监听端口和守护线程
            SSLServerSocketFactory factory = ssl.createFactory();
            if (Validates.isEmpty(listenerIp))
                serverSocket = (SSLServerSocket)factory.createServerSocket(listenerPort);
            else
                serverSocket = (SSLServerSocket)factory.createServerSocket(listenerPort, 50, InetAddress.getByName(listenerIp));
            serverSocket.setReuseAddress(true);
            serverSocket.setNeedClientAuth(ssl.isClientAuth());
            serverSocket.setWantClientAuth(ssl.isClientAuth());
            return true;
        }
        catch(Exception e)
        {
            log.error("初始化SSLServerSocket时异常", e);
            throw Asserts.exception(e);
        }
    }

    @Override /** 线程关闭之后 */
    protected void closeAfter()
    {
        //1.关闭监听
        if (serverSocket != null)
        {
            Closes.closeIgnoreException(serverSocket);
            serverSocket = null;
        }
        
        //2.关闭定时任务等父类定义
        super.closeAfter();
        
        //3.打印退出日志
        log.info(toString() + "退出");
    }

    @Override /** 线程持续运行 */
    protected void loop()
    {
        try
        {
            Socket socket = serverSocket.accept();
            Sockets.setOption(socket, true, soTimeoutMs, soLingerS, false);
            
            SSLSocket sslSocket = (SSLSocket)socket;
            SSLSession sslSession = sslSocket.getSession();
            String cipherSuite = sslSession.getCipherSuite();
            if (cipherSuite == null || SSL_NULL_WITH_NULL_NULL.equals(cipherSuite))
            {//HTTP访问，未使用SSL
                socket.close();
                return;
            }
            
            String sslSessionId = null;
            X509Certificate[] x509Certs = null;
            HttpCertificate sslSessionCache = (HttpCertificate)sslSession.getValue(SSL_SESSION_CACHE);
            if (sslSessionCache != null)
            {
                x509Certs = sslSessionCache.getCerts();
                sslSessionId = sslSessionCache.getSessionId();
            }
            else
            {
                x509Certs = getCertChain(sslSession);
                byte[] bytes = sslSession.getId();
                sslSessionId = Hexs.toHexString(bytes);
                sslSessionCache = new HttpCertificate(sslSessionId, x509Certs);
                sslSession.putValue(SSL_SESSION_CACHE, sslSessionCache);
            }
            
            if (x509Certs == null && ssl.isClientAuth())
            {
                socket.close();
                return;
            }
            
            //连接交给控制器多线程处理
            controller.execute(add(newConnection(socket, x509Certs)));
        }
        catch (SocketException e)
        {
            if (!_SOCKET_CLOSED_.equalsIgnoreCase(e.getMessage()))
            {//非服务端自己关闭连接导致的异常打印
                log.error(toString() + "SocketException异常", e);
            }
        }
        catch (IOException e)
        {
            log.error(toString() + "IO异常", e);
        }
    }
    
    /** 新建连接 */
    private HttpBioConnection newConnection(Socket socket, X509Certificate[] x509Certs) throws IOException
    {//把输入输出流的引用先拿出来
        return new HttpBioConnection(this, socket, socket.getInputStream(), socket.getOutputStream(), x509Certs);
    }
    
    /**
     * 通过SSLSession获取X509证书链
     * 
     * @param sslSession    SSL会话
     * @return              X509证书链
     */
    private X509Certificate[] getCertChain(SSLSession sslSession)
    {
        try
        {
            javax.security.cert.X509Certificate javaxCerts[] = sslSession.getPeerCertificateChain();
            if (javaxCerts == null || javaxCerts.length == 0)
                return null;

            int length = javaxCerts.length;
            X509Certificate[] javaCerts = new X509Certificate[length];

            CertificateFactory cf = CertificateFactory.getInstance(_X_509_);
            for (int i = 0; i < length; i++)
            {
                byte bytes[] = javaxCerts[i].getEncoded();
                ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
                javaCerts[i] = (X509Certificate) cf.generateCertificate(stream);
            }

            return javaCerts;
        }
        catch (SSLPeerUnverifiedException pue)
        {
            return null;
        }
        catch (Exception e)
        {
            return null;
        }
    }
}
